/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools mkfs super block writer
 */

#include "super_block_writer.h"

#include <cinttypes>
#include <cmath>
#include <string>
#include <sys/types.h>
#include <uuid.h>

#include "device_manager.h"
#include "hmfs_common.h"
#include "hmfs_encoding.h"
#include "hmfs_io.h"
#include "hmfs_log.h"
#include "hmfs_zoned.h"
#include "mkfs_format.h"
#include "securec.h"

namespace OHOS {
namespace Hmfs {
namespace {
const std::vector<std::string> DEFAULT_COLD_EXT_LIST = {
    "mp", "wm", "og", "jp", // common
    "avi", "m4v", "m4p", "mkv", "mov", "webm", // video
    "wav", "m4a", "3gp", "opus", "flac", // audio
    "gif", "png", "svg", "webp", // image
    "jar", "deb", "iso", "gz", "xz", "zst", // archives
    "pdf", "pyc", "ttc", "ttf", "exe", // other
    "apk", "cnt", "exo", "odex", "vdex", "so",
};

const std::vector<std::string> DEFAULT_HOT_EXT_LIST = {
    "db",
    "vmdk",
    "vdi",
    "qcow2",
};
}

SuperBlockWriter::SuperBlockWriter(MkfsFormat &mkfs) : mkfs_(mkfs) {}

int32_t SuperBlockWriter::Format()
{
    superBlock_ = std::make_unique<SuperBlockOnDisk>();
    (void)memset_s(superBlock_.get(), sizeof(SuperBlockOnDisk), 0, sizeof(SuperBlockOnDisk));

    if (FillSuperBlockData()) {
        return -1;
    }

    if (UpdateHmfsData()) {
        return -1;
    }

    if (Write()) {
        return -1;
    }

    return 0;
}

bool NearlyEqual(double a, double b, double epsilon = 0.000001)
{
    return std::fabs(a - b) < epsilon;
}

int32_t SuperBlockWriter::UpdateHmfsData()
{
    CmdConfig &cfgPara = mkfs_.cfgPara_;
    HmfsData &hmfsData = mkfs_.hmfsData_;
    SuperBlockData *superBlock = &superBlock_->superBlock;

    if (cfgPara.features & HMFS_FEATURE_RO) {
        cfgPara.overProvision = 0;
        hmfsData.reservedSegments = 0;
    } else {
        uint32_t usableSegs = HmfsZoned::GetInstance().HmfsGetUsableSegments(superBlock);
        if (NearlyEqual(cfgPara.overProvision, 0.0)) {
            cfgPara.overProvision = HmfsCommon::GetInstance().GetBestOverProvision(superBlock);
        }
        if (NearlyEqual(cfgPara.overProvision, 0.0)) {
            HMFS_ERROR("device size is not sufficient for HMFS volume");
            return -1;
        }

        const uint32_t overProvisionTimes = 2;
        hmfsData.reservedSegments =
            (overProvisionTimes * (PERCENT_TIMES / cfgPara.overProvision + 1) + CURSEG_TYPE_MAX) *
            AlignUpCount(usableSegs, hmfsData.sectionCount);
    }

    if ((!(cfgPara.features & HMFS_FEATURE_RO) && cfgPara.overProvision == 0) ||
        ((hmfsData.segmentCountInMain - CURSEG_TYPE_MAX) < hmfsData.reservedSegments)) {
        HMFS_ERROR("device size is not sufficient for HMFS volume");
        HMFS_DEBUG("cfgPara.overProvision = %f", cfgPara.overProvision);
        HMFS_DEBUG("hmfsData.segmentCount = %u", hmfsData.segmentCount);
        HMFS_DEBUG("hmfsData.segmentCountInMain = %u, CURSEG_TYPE_MAX = %u, hmfsData.reservedSegments = %u",
            hmfsData.segmentCountInMain, CURSEG_TYPE_MAX, hmfsData.reservedSegments);
        return -1;
    }

    return 0;
}

int32_t SuperBlockWriter::FillSuperBlockDataEnd(SuperBlockData *superBlock)
{
    HMFS_CHECK(superBlock != nullptr, return -1, "Failed to fill super block data");

    CmdConfig &cfgPara = mkfs_.cfgPara_;
    HmfsData &hmfsData = mkfs_.hmfsData_;
    if (cfgPara.features & HMFS_FEATURE_LOST_FOUND) {
        hmfsData.lpfInodeId = hmfsData.nextFreeInodeId++;
    }

    SetLeValue(superBlock->checkPointPayload, hmfsData.checkPointPayload);
    (void)memcpy_s(superBlock->version, VERSION_TOTAL_LEN, hmfsData.version, VERSION_TOTAL_LEN);
    (void)memcpy_s(superBlock->initVersion, VERSION_TOTAL_LEN, hmfsData.version, VERSION_TOTAL_LEN);
    SetLeValue(superBlock->features, cfgPara.features);

    uint32_t deviceCount = DeviceManager::GetInstance().GetDeviceCount();
    if (deviceCount > 1) {
        for (uint32_t id = 0; id < deviceCount; id++) {
            DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(id);
            if (deviceInfo == nullptr) {
                HMFS_ERROR("failed to get device info, id %u", id);
                return -1;
            }
            int32_t ret = strcpy_s(superBlock->devices[id].path, DEVICE_PATH_MAX_LEN, deviceInfo->path.c_str());
            HMFS_CHECK(ret == 0, return -1, "Failed to copy device path");
            superBlock->devices[id].segmentCount = deviceInfo->segmentCount;
        }
    }

    if (cfgPara.uuid.empty()) {
        uuid_generate(superBlock->uuid);
    } else {
        if (uuid_parse(cfgPara.uuid.c_str(), superBlock->uuid)) {
            HMFS_ERROR("Supplied string is not a valid UUID.");
            return -1;
        }
    }

    if (cfgPara.features & HMFS_FEATURE_INODE_CHKSUM) {
        hmfsData.checksumSeed =
            HmfsCommon::GetInstance().CalculateCrc32(~0, superBlock->uuid, sizeof(superBlock->uuid));
    }

    std::u16string volumeNameUtf16 = Utf8ToUtf16(cfgPara.volume);
    if (memcpy_s(superBlock->volumeName, VOLUME_NAME_MAX_LEN * sizeof(char16_t),
        volumeNameUtf16.c_str(), volumeNameUtf16.size() * sizeof(char16_t)) != 0) {
        HMFS_ERROR("Failed to copy volume name");
        return -1;
    }

    FillExtList();

    if (cfgPara.features & HMFS_FEATURE_CASEFOLD) {
        SetLeValue(superBlock->encoding, cfgPara.sEncoding);
        SetLeValue(superBlock->encodingFlags, cfgPara.sEncodingFlags);
    }

    return 0;
}

int32_t SuperBlockWriter::FillSuperBlockData()
{
    CmdConfig &cfgPara = mkfs_.cfgPara_;
    HmfsData &hmfsData = mkfs_.hmfsData_;
    SuperBlockData *superBlock = &superBlock_->superBlock;

    SetLeValue(superBlock->magicNumber, HMFS_MAGIC_NUMBER);
    SetLeValue(superBlock->majorVersion, HMFS_MAJOR_VERSION);
    SetLeValue(superBlock->minorVersion, HMFS_MINOR_VERSION);

    uint32_t logSectorSize = HmfsCommon::GetInstance().LogBase2(hmfsData.sectorSize);
    uint32_t logSectorsPerBlock = HmfsCommon::GetInstance().LogBase2(hmfsData.sectorsPerBlk);
    uint32_t logBlockSize = logSectorSize + logSectorsPerBlock;
    uint32_t logBlksPerSeg = HmfsCommon::GetInstance().LogBase2(BLOCKS_PER_SEGMENT);
    SetLeValue(superBlock->logSectorSize, logSectorSize);
    SetLeValue(superBlock->log2SectorsPerBlock, logSectorsPerBlock);
    SetLeValue(superBlock->logBlockSize, logBlockSize);
    SetLeValue(superBlock->logBlksPerSeg, logBlksPerSeg);
    SetLeValue(superBlock->segsPerSection, cfgPara.segsPerSection);
    SetLeValue(superBlock->sectionsPerZone, cfgPara.sectionsPerZone);
    SetLeValue(superBlock->blockCount, hmfsData.sectorCount >> logSectorsPerBlock);
    SetLeValue(superBlock->sectionCount, hmfsData.sectionCount);
    SetLeValue(superBlock->segmentCount, hmfsData.zoneAlignedSegCount);
    SetLeValue(superBlock->segmentCountInCP, hmfsData.segmentCountInCP);
    SetLeValue(superBlock->segmentCountInSIT, hmfsData.segmentCountInSIT);
    SetLeValue(superBlock->segmentCountInNAT, hmfsData.segmentCountInNAT);
    SetLeValue(superBlock->segmentCountInSSA, hmfsData.segmentCountInSSA);
    SetLeValue(superBlock->segmentCountInMain, hmfsData.segmentCountInMain);
    SetLeValue(superBlock->segment0BlockId, hmfsData.segment0BlockId);
    superBlock->checkPointBlockId = superBlock->segment0BlockId;
    SetLeValue(superBlock->sitBlockId, hmfsData.sitStartBlkId);
    SetLeValue(superBlock->natBlockId, hmfsData.natStartBlkId);
    SetLeValue(superBlock->ssaBlockId, hmfsData.natStartBlkId + hmfsData.segmentCountInNAT * BLOCKS_PER_SEGMENT);
    SetLeValue(superBlock->mainBlockId, hmfsData.mainStartBlkId);
    SetLeValue(superBlock->rootInodeId, hmfsData.rootInode);
    SetLeValue(superBlock->nodeInodeId, hmfsData.nodeInode);
    SetLeValue(superBlock->metaInodeId, hmfsData.metaInode);

    for (int32_t qtype = 0; qtype < MAX_QUOTAS; qtype++) {
        if (((1 << qtype) & cfgPara.quotaBits)) {
            SetLeValue(superBlock->quotaInodeId[qtype], hmfsData.nextFreeInodeId++);
            HMFS_INFO("add quota type = %u => %u\n", qtype, hmfsData.nextFreeInodeId - 1);
        }
    }

    return FillSuperBlockDataEnd(superBlock);
}

void SuperBlockWriter::FillExtList()
{
    CmdConfig &cfgPara = mkfs_.cfgPara_;

    SuperBlockData *superBlock = &superBlock_->superBlock;
    std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> extList = {
        std::make_pair(DEFAULT_COLD_EXT_LIST, cfgPara.coldExtList),
        std::make_pair(DEFAULT_HOT_EXT_LIST, cfgPara.hotExtList),
    };

    uint32_t index = 0;
    uint32_t coldExtCount = 0;
    for (size_t i = 0; i < extList.size(); i++) {
        for (auto &ext : extList[i].first) {
            if (index >= EXTENSION_COUNT_MAX) {
                break;
            }
            int32_t ret = strcpy_s(superBlock->extensionList[index++], EXTENSION_LEN_MAX, ext.c_str());
            HMFS_CHECK(ret == 0, return, "Failed to copy superBlock extensionList");
        }

        for (auto &ext : extList[i].second) {
            if (index >= EXTENSION_COUNT_MAX) {
                break;
            }

            if (!IsExtensionDuplicate(ext)) {
                int32_t ret = strcpy_s(superBlock->extensionList[index++], EXTENSION_LEN_MAX, ext.c_str());
                HMFS_CHECK(ret == 0, return, "Failed to copy superBlock extensionList");
            }
        }

        if (i == 0) {
            coldExtCount = index;
        }
    }

    uint32_t hotExtCount = index - coldExtCount;
    SetLeValue(superBlock->hotExtensionCount, hotExtCount);
    SetLeValue(superBlock->coldExtensionCount, coldExtCount);
}

bool SuperBlockWriter::IsExtensionDuplicate(std::string &ext)
{
    SuperBlockData *superBlock = &superBlock_->superBlock;
    for (uint32_t i = 0; i < EXTENSION_COUNT_MAX; i++) {
        if (strcmp(superBlock->extensionList[i], ext.c_str()) == 0) {
            return true;
        }
    }
    return false;
}

int32_t SuperBlockWriter::ClacCheckSum()
{
    CmdConfig &cfgPara = mkfs_.cfgPara_;
    SuperBlockData *superBlock = &superBlock_->superBlock;

    if (cfgPara.features & HMFS_FEATURE_SB_CHKSUM) {
        SetLeValue(superBlock->checksumOffset, SB_CHECKSUM_OFFSET);
        SetLeValue(superBlock->checksum,
            HmfsCommon::GetInstance().CalculateCrc32(HMFS_MAGIC_NUMBER, superBlock, SB_CHECKSUM_OFFSET));
        HMFS_INFO("super block checksum is set: offset (%u), crc (0x%x)",
            GetLeValue(superBlock->checksumOffset), GetLeValue(superBlock->checksum));
    }

    return 0;
}

int32_t SuperBlockWriter::Write()
{
    ClacCheckSum();

    HMFS_DEBUG("Writing super block data at blockid 0, count %u", HMFS_SUPER_BLOCK_COUNT);
    for (uint64_t i = 0; i < HMFS_SUPER_BLOCK_COUNT; i++) {
        if (HmfsIo::GetInstance().HmfsIo::GetInstance().DevWriteBlock(superBlock_.get(), i)) {
            HMFS_ERROR("failed to write super block [%" PRIu64 "] on disk", i);
            return -1;
        }
    }
    return 0;
}

} // namespace Hmfs
} // namespace OHOS
